Skip to main content

Migrations 数据迁移

Sequelize v2.0中引入了一个新的CLI(命令行工具),就像使用 Git/SVN 管理代码一样,你可以使用迁移功能(Migrations)来跟踪数据库的更改。通过迁移功能,你可以将现有数据库转移到另一个状态,反之亦然。进行迁移时,状态转换会被保存到迁移文件中,这些文件描述了如何进入新状态以及如何恢复更改以恢复到旧状态。

  1. CLI
  • 1.1 CLI安装

  • 1.2 引导

  • 1.3 创建第一个模型(并迁移)

  • 1.4 执行迁移

  • 1.5 撤消迁移

  • 1.6 创建第一个种子

  • 1.7 执行种子

  • 1.8 撤消种子

  • 1.9 CLI使用帮助

  1. 高级主题
  • 2.1 迁移结构

  • 2.2 .sequelizerc文件

  • 2.3 动态配置

  • 2.4 使用环境变量

  • 2.5 指定方言选项

  • 2.6 生产用途

  • 2.7 存储

  • 2.8 配置连接字符串

  • 2.9 使用SSL连接

  • 2.10 程序化使用

  1. queryInterface对象及功能

迁移需要使用Sequelize CLI,CLI提供了对迁移和项目引导的支持。

  1. CLI

1.1 CLI安装

要使用CLI首先要安装相应的包:

$ npm install --save sequelize-cli

当通过-g参数全局安装npm包时,可以在命令行中通过sequelize [command]命令来使用CLI。而没有使全局安装时,就可以通过node_modules/.bin/sequelize [command]来使用CLI。

1.2 引导

在上一步中,我们创建了一个空项目,这里需要执行init命令:

$ node_modules/.bin/sequelize init

以上命令会创建以下文件夹:

  • config:包含配置文件,这些文件会告诉CLI怎样连接数据库

  • models:包含项目中的所有模型(Model)

  • migrations:包含所有迁移文件

  • seeders:包含所有种子文件

注意:以上命令使用到sequelize模块,如果你项目中没有请使用npm命令安装;另外,还需要安装对应的方言模块,以实现底层的数据库操作,如:使用MySQL数据库时就需要安装mysql2模块。

配置

继续后续操作前,我们需要使CLI能够连接到数据库,可以在config/config.json文件中配置数据库。该文件类似如下:

{ "development": { "username": "root", "password": null, "database": "database_development", "host": "127.0.0.1", "dialect": "mysql" }, "test": { "username": "root", "password": null, "database": "database_test", "host": "127.0.0.1", "dialect": "mysql" }, "production": { "username": "root", "password": null, "database": "database_production", "host": "127.0.0.1", "dialect": "mysql" } }

编辑该文件,正确设置数据库的验证信息和方言(数据库类型)。

注意:如果你配置的数据库还不存在,调用db:create命令即可自动创建。

1.3 创建第一个模型(并迁移)

正确配置CLI的配置文件后,就可以创建你的第一个迁移。可以通过一个命令来轻松的完成这一操作。

创建模型使用model:generate命令,该命令包含以下两个参数:

  • name-模型名

  • attributes-模型属性列表

接下来我们来创建一个名为User的模型:

node_modules/.bin/sequelize model:generate --name User --attributes firstName:string,lastName:string,email:string

以上命令会做以下两件事:

  • 在models文件夹下创建模型文件user

  • 在migrations文件夹下创建名称类似XXXXXXXXXXXXXX-create-user.js的迁移文件

注意:Sequelize只会使用模型文件,模型文件是对数据库中表的表示;迁移文件是对该模型的更改,或者说是CLI所要使用的表;而“迁移”可以认为对数据库修改的一次提交或日志。

1.4 执行迁移

到目前为目,我们尚未在数据库中插入任何内容。刚刚我们为第一个模型User创建了所需的模型和迁移文件。

现在要在数据库中创建该表,这时需要执行db:migrate命令:

$ node_modules/.bin/sequelize db:migrate

以上操作会执行以下步骤:

  • 确保数据库中有一个名为SequelizeMeta的表,此表用于记录在当前数据库上运行的迁移

  • 查找尚示执行的迁移文件,这一步通过SequelizeMeta表来实现。在本例中,将执行上一步中创建的XXXXXXXXXXXXXX-create-user.js迁移文件。

  • 创建一个名为Users的表,其中包含迁移文件中指定的所有列。

1.5 撤消迁移

现在我们的表已在数据库中创建并保存。通过迁移功能,只需运行命令即可恢复到旧状态。

撤消迁移可以使用db:migrate:undo命令,此命令将还原到最近的迁移:

$ node_modules/.bin/sequelize db:migrate:undo

此外,可以通过db:migrate:undo:all命令来撤消所有迁移来恢复到初始状态。还可以通过--to选项来传递迁移文件名称,以恢复到特定迁移。

$ node_modules/.bin/sequelize db:migrate:undo:all --to XXXXXXXXXXXXXX-create-posts.js

1.6 创建第一个种子

有些情况下我们可能需要在一个表中插入一些默认数据。如:在前面的User表中创建一个演示用户。

要管理所有数据迁移,可以使用seeders。种子文件表示数据的一些变化,可用于使用样本数据或测试数据填充数据库表。

创建种子文件可以使用seed:generate命令。现在我们创建一个种子文件,它会添加一个演示用户到User表中:

$ node_modules/.bin/sequelize seed:generate --name demo-user

这个命令会在seeders文件夹中创建一个种子文件,文件名类似XXXXXXXXXXXXXX-demo-user.js。它遵循与迁移文件相同的up/down语义。

现在编辑这个文件,以添加一个演示用户到User表:

'use strict';

module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.bulkInsert('Users', [{ firstName: 'John', lastName: 'Doe', email: 'demo@demo.com' }], {}); },

down: (queryInterface, Sequelize) => { return queryInterface.bulkDelete('Users', null, {}); } };

1.7 执行种子

上一步我们创建了种子文件,但它还未提交到数据库中。这时可以通过db:seed:all命令实现:

$ node_modules/.bin/sequelize db:seed:all

以上命令会执行种子文件,演示用户也会被添加到User表中。

注意:种子执行不像迁移,它不会存储在任何位置(迁移会存储在SequelizeMeta中),如果需要存储请参阅Storage一节。

1.8 撤消种子

使用种子存储后,种子的执行可以撤消。可以通过以下两个命令实现:

db:seed:undo命令撤消最近的一次种子操作:

node_modules/.bin/sequelize db:seed:undo

db:seed:undo:all命令撤消所有种子操作:

node_modules/.bin/sequelize db:seed:undo:all

1.9 CLI使用帮助

可以通过sequelize help查看使用帮助,该命令会输出当前运行环境中的Node版本、CLI版本、ORM版本及当前版本CLI所支持的命令。

类似如下:

Sequelize CLI [Node: 10.14.2, CLI: 5.4.0, ORM: 4.42.0]

sequelize [命令]

命令: sequelize db:migrate 运行待执行的迁移 sequelize db:migrate:schema:timestamps:add 更新迁移表以获取时间戳 sequelize db:migrate:status 列出所有迁移的状态 sequelize db:migrate:undo 恢复迁移 sequelize db:migrate:undo:all 恢复所有迁移 sequelize db:seed 运行指定的种子 sequelize db:seed:undo 撤消最近执行的种子 sequelize db:seed:all 运行所有种子 sequelize db:seed:undo:all 撤消所有已执行的种子 sequelize db:create 创建配置中指定的数据库 sequelize db:drop 删除配置中指定的数据库 sequelize init 初始化项目 sequelize init:config 初始化配置 sequelize init:migrations 初始化迁移 sequelize init:models 初始化模型 sequelize init:seeders 初始化种子 sequelize migration:generate 生成新的迁移文件 [aliases: migration:create] sequelize model:generate 生成一个模模型及期迁移文件 [aliases: model:create] sequelize seed:generate 生成一个新的种子文件 [aliases: seed:create]

选项: --help 显示帮助信息 [布尔] --version 显示版本号 [布尔]

  1. 高级主题

2.1 迁移结构

所有迁移都在项目顶层一个名为migrations的目录中。

sequelize会生成迁移的结构,以下是一个典型的迁移文件结构:

module.exports = { up: function(queryInterface, Sequelize) { // 转换为新状态的逻辑 },

down: function(queryInterface, Sequelize) { // 恢复修改的逻辑 } }

通过queryInterface对象可用来修改数据库。Sequelize对象中存储了数据类型,如:STRING和INTEGER。up和down函数需要返回一个Promise。以下是一些示例代码:

module.exports = { up: (queryInterface, Sequelize) => { return queryInterface.createTable('Person', { name: Sequelize.STRING, isBetaMember: { type: Sequelize.BOOLEAN, defaultValue: false, allowNull: false } }); }, down: (queryInterface, Sequelize) => { return queryInterface.dropTable('Person'); } }

可以通过queryInterface了节查看该对象所支持的方法。

2.2 .sequelizerc文件

.sequelizerc文件一个特殊的配置文件,它允许你指定通常作为参数传递给CLI的各种选项。可以使用它的一些场景:

  • 需要重写默认的migrations, models, seeders或config文件夹。

  • 需要重命名config.json时。如:重命名为database.json或其它。

除以上说明外,还有更多的应用场景。接下来,我们来看看如何使用这个文件来进行自定义配置。

首先,在项目根目录下创建.sequelizerc文件:

$ touch .sequelizerc

以下是一个示例配置:

const path = require('path');

module.exports = { 'config': path.resolve('config', 'database.json'), 'models-path': path.resolve('db', 'models'), 'seeders-path': path.resolve('db', 'seeders'), 'migrations-path': path.resolve('db', 'migrations') }

在这个配置中我们告诉CLI:

  • 使用config/database.json进行配置设置

  • 使用db/models做为模型目录

  • 使用db/seeders做为种子目录

  • 使用db/migrations做为迁移目录

2.3 动态配置

配置文件默认是一个名为config.json的JSON文件,但有时你想执行一些代码或访问环境变量,这些操作在JSON文件中是不可能实现的。

Sequelize CLI可以从JSON或JS文件中读取配置,这可以通过.sequelizerc文件配置。

要使用JS格式的文件做为配置文件,可以在.sequelizerc文件中配置如下:

const path = require('path');

module.exports = {
'config': path.resolve('config', 'config.js')
}

现在Sequelize CLI会加载config/config.js文件获取配置选项,这样通过这个JS文件可以执行任何代码并导出最终的动态配置文件。示例如下:

const fs = require('fs');

module.exports = {
development: {
username: 'database_dev',
password: 'database_dev',
database: 'database_dev',
host: '127.0.0.1',
dialect: 'mysql'
},
test: {
username: 'database_test',
password: null,
database: 'database_test',
host: '127.0.0.1',
dialect: 'mysql'
},
production: {
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
host: process.env.DB_HOSTNAME,
dialect: 'mysql',
dialectOptions: {
ssl: {
ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt')
}
}
}
};

2.4 使用环境变量

使用CLI,可以直接访问config/config.js中的环境变量。

如下所示,可以在该文件中直接使用环境变量并导出:

module.exports = {
development: {
username: 'database_dev',
password: 'database_dev',
database: 'database_dev',
host: '127.0.0.1',
dialect: 'mysql'
},
test: {
username: process.env.CI_DB_USERNAME,
password: process.env.CI_DB_PASSWORD,
database: process.env.CI_DB_NAME,
host: '127.0.0.1',
dialect: 'mysql'
},
production: {
username: process.env.PROD_DB_USERNAME,
password: process.env.PROD_DB_PASSWORD,
database: process.env.PROD_DB_NAME,
host: process.env.PROD_DB_HOSTNAME,
dialect: 'mysql'
}

2.5 指定方言选项

有时你需要指定dialectOption选项,一般你可以在config/config.json中添加它。而当需要使用代码来获取该选项时,可以使用动态配置文件来处理。

{
"production": {
"dialect":"mysql",
"dialectOptions": {
"bigNumberStrings": true
}
}
}

备注:Sequelize中方言(dialect)选项用于指定所使用的数据库型。

2.6 生产用途

以下是一些有关在生产环境中使用CLI和迁移设置的提示。

1)使用环境变量进行配置设置,使用动态配置可以更好地实现。以下是一个在生产环境中使用安全配置的设置示例:

const fs = require('fs');

module.exports = {
development: {
username: 'database_dev',
password: 'database_dev',
database: 'database_dev',
host: '127.0.0.1',
dialect: 'mysql'
},
test: {
username: 'database_test',
password: null,
database: 'database_test',
host: '127.0.0.1',
dialect: 'mysql'
},
production: {
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
host: process.env.DB_HOSTNAME,
dialect: 'mysql',
dialectOptions: {
ssl: {
ca: fs.readFileSync(__dirname + '/mysql-ca-master.crt')
}
}
}
};

2.7 存储

CLI支持三种存储方式:sequelize,json,none

  • sequelize : 在sequelize数据库的表中存储迁移和种子

  • json : 在json文件中存储迁移和种子

  • none : 不存储任何迁移、种子

迁移存储

默认情况下,CLI会在数据库中创建一个名为SequelizeMeta的表,其中包含每个已执行迁移的条目。要更改此行为,可以将三个选项添加到配置文件中。使用,您可以选择。 如果选择json,则可以使用migrationStoragePath指定文件的路径,或者CLI将写入文件sequelize-meta.json。 如果要使用sequelize将信息保留在数据库中,但希望使用其他表,则可以使用migrationStorageTableName更改表名。

  • migrationStorage - 用于指定要用于存储迁移的类型,默认为:sequelize,如果使用json则需要指定migrationStoragePath

  • json - 使用json做为迁移存储时,指定JSON文件的路径。默认为:sequelize-meta.json

  • migrationStorageTableName - 使用sequelize做为迁移存储时,用于指定存储表名。默认为:migrationStorageTableName

{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql",

// 指定存储类型,默认: sequelize
"migrationStorage": "json",

// 指定存储文件名,默认: sequelize-meta.json
"migrationStoragePath": "sequelizeMeta.json",

// 指定存储表名,默认: SequelizeMeta
"migrationStorageTableName": "sequelize_meta"
}
}

备注:不建议使用none用作迁移存储。如果决定使用,应该充分考虑未记录迁移执行或未运行的影响。

种子存储

默认情况下,CLI不存储种子的执行记录。如果需要存储,可以使用seederStorage选项来配置,种子存储的各设置选项与迁移存储类似。当使用json存储时,可以使用seederStoragePath来指定路径,或者CLI使用默认的sequelize-data.json;如果需要存储在数据库中,则可以使用sequelize选项,这时可以通过seederStorageTableName来指定表名,默认将使用SequelizeData。

{
"development": {
"username": "root",
"password": null,
"database": "database_development",
"host": "127.0.0.1",
"dialect": "mysql",
// Use a different storage. Default: none
"seederStorage": "json",
// Use a different file name. Default: sequelize-data.json
"seederStoragePath": "sequelizeData.json",
// Use a different table name. Default: SequelizeData
"seederStorageTableName": "sequelize_data"
}
}

2.8 配置连接字符串

可以使用--config选项来替代数据库配置文件,可以使用--url选项传入连接字符串。例如:

$ node_modules/.bin/sequelize db:migrate --url 'mysql://root:password@mysql_host.com/database_name'

2.9 使用SSL连接

使用SSL连接时,应确保在dialectOptions和基本配置中都指定了ssl。

{
"production": {
"dialect":"postgres",
"ssl": true,
"dialectOptions": {
"ssl": true
}
}
}

2.10 程序化使用

Sequelize有一个姐妹库:umzug,可以用编程方式处理迁移任务的执行和记录。

3. queryInterface对象及功能

QueryInterface是Sequelize的内置类之一,可以通过Sequelize对象(Sequelize实例)的getQueryInterface()方法获取该类的实例(即:queryInterface对象):

var queryInterface = sequelize.getQueryInterface();

通过queryInterface对象中的方法可以对数据进行修改,其中一些方法可以修改数据库的schema,以下是该类中的部分方法说明:

createTable(tableName, attributes, options)

通过传入的表名tableName、属性attributes及其它选项options创建新表。

createTable()方法用于在数据库中创建新表。创建表时可以通过attributes属性定义一些简单或复杂的属性;而options参数,可以用于定义表所使用的编码及引擎等。

queryInterface.createTable(
'nameOfTheNewTable',
{
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
createdAt: {
type: Sequelize.DATE
},
updatedAt: {
type: Sequelize.DATE
},
attr1: Sequelize.STRING,
attr2: Sequelize.INTEGER,
attr3: {
type: Sequelize.BOOLEAN,
defaultValue: false,
allowNull: false
},
//foreign key usage
attr4: {
type: Sequelize.INTEGER,
references: {
model: 'another_table_name',
key: 'id'
},
onUpdate: 'cascade',
onDelete: 'cascade'
}
},
{
engine: 'MYISAM', // default: 'InnoDB'
charset: 'latin1' // default: null
}
)

dropTable(tableName, options)

删除已存在的表

queryInterface.dropTable('nameOfTheExistingTable')

dropAllTables(options)

删除数据库中所有已存在的表

queryInterface.dropAllTables()

renameTable(before, after, options)

重命名已存在的表

queryInterface.renameTable('Person', 'User')

showAllTables(options)

返回数据库中所有已存在的表的表名

queryInterface.showAllTables().then(function(tableNames) {})

describeTable(tableName, options)

返回表描述,即:返回的一个包含所有属性信息的哈希表

queryInterface.describeTable('Person').then(function(attributes) {
/*
attributes 结构类似如下:

{
name: {
type: 'VARCHAR(255)', // this will be 'CHARACTER VARYING' for pg!
allowNull: true,
defaultValue: null
},
isBetaMember: {
type: 'TINYINT(1)', // this will be 'BOOLEAN' for pg!
allowNull: false,
defaultValue: false
}
}
*/
})

addColumn(tableName, attributeName, dataTypeOrOptions, options)

向已存在的表中添加新列,数据类型可以简单或详细定义

queryInterface.addColumn(
'nameOfAnExistingTable',
'nameOfTheNewAttribute',
Sequelize.STRING
)

// or

queryInterface.addColumn(
'nameOfAnExistingTable',
'nameOfTheNewAttribute',
{
type: Sequelize.STRING,
allowNull: false
}
)

// or with an explicit schema:

queryInterface.addColumn({
tableName: 'Person',
schema: 'public'
},
'signature',
Sequelize.STRING
)

removeColumn(tableName, attributeName, options)

移除已存在的表中指定的列

queryInterface.removeColumn('Person', 'signature')

// or with an explicit schema:

queryInterface.removeColumn({
tableName: 'Person',
schema: 'public'
}, 'signature');

changeColumn(tableName, attributeName, dataTypeOrOptions, options)

修改属性的元数据。可以修改默认值、是否允许空或数据类型,修改时应该确保完全描述了新的数据类型。

queryInterface.changeColumn(
'nameOfAnExistingTable',
'nameOfAnExistingAttribute',
{
type: Sequelize.FLOAT,
allowNull: false,
defaultValue: 0.0
}
)

renameColumn(tableName, attrNameBefore, attrNameAfter, options)

重命令属性(修改列名)

queryInterface.renameColumn('Person', 'signature', 'sig')

addIndex(tableName, attributes, options)

通过指定的属性向表中索引。当没有传入options时,会自动创建索引名。

// This example will create the index person_firstname_lastname
queryInterface.addIndex('Person', ['firstname', 'lastname'])

// This example will create a unique index with the name SuperDuperIndex using the optional 'options' field.
// Possible options:
// - indicesType: UNIQUE|FULLTEXT|SPATIAL
// - indexName: The name of the index. Default is __
// - parser: For FULLTEXT columns set your parser
// - indexType: Set a type for the index, e.g. BTREE. See the documentation of the used dialect
// - logging: A function that receives the sql query, e.g. console.log
// - where: A hash of attributes to limit your index(Filtered Indexes - MSSQL & PostgreSQL only)
queryInterface.addIndex(
'Person',
['firstname', 'lastname'],
{
indexName: 'SuperDuperIndex',
indicesType: 'UNIQUE'
}
)

queryInterface.addIndex(
'Person',
['firstname', 'lastname'],
{
where: {
lastname: {
$ne: null
}
}
}
)

removeIndex(tableName, indexNameOrAttributes, options)

移除表中已存在的索引

queryInterface.removeIndex('Person', 'SuperDuperIndex')

// or

queryInterface.removeIndex('Person', ['firstname', 'lastname'])

addConstraint(tableName, attributes, options)

V4.0.0+

添加一个新约束。

  • tableName - 要添加约束的表的表名

  • attributes - 应用约束的列名数组

  • options - 一个包含约束定义的对象,如:约束名、类型等

options中可用的选项有:

  • type - 约束类型。可用的约束类型有:

  • UNIQUE

  • DEFAULT (MSSQL only)

  • CHECK (MySQL - Ignored by the database engine )

  • FOREIGN KEY

  • PRIMARY KEY

  • name - 约束名。如果不指定,sequelize将根据约束类型、表名、列名自动命名

  • defaultValue - 约束默认值

  • where - 约束检查条件

  • references - Object。指定目标表、列名以创建外键约束

  • references.table - 目标表名或表

  • references.field - 目录列名 Available constraints:

//UNIQUE
queryInterface.addConstraint('Users', ['email'], {
type: 'unique',
name: 'custom_unique_constraint_name'
});

//CHECK
queryInterface.addConstraint('Users', ['roles'], {
type: 'check',
where: {
roles: ['user', 'admin', 'moderator', 'guest']
}
});

//Default - MSSQL only
queryInterface.addConstraint('Users', ['roles'], {
type: 'default',
defaultValue: 'guest'
});

//Primary Key
queryInterface.addConstraint('Users', ['username'], {
type: 'primary key',
name: 'custom_primary_constraint_name'
});

//Foreign Key
queryInterface.addConstraint('Posts', ['username'], {
type: 'FOREIGN KEY',
references: { //Required field
table: 'target_table_name',
field: 'target_column_name'
},
onDelete: 'cascade',
onUpdate: 'cascade'
});

removeConstraint(tableName, constraintName, options)

V4.0.0+

移除表中已经存在的约束

queryInterface.removeConstraint('Users', 'my_constraint_name');

showConstraint(tableName, options)

返回表中已经存在的约束列表

V4.0.0+

queryInterface.showConstraint('Users');
// Returns array of objects/constraints

变更记录

  • [2017-06-10] 基于v4.1.0首次发布

  • [2019-01-21] 基于v4.42.0更新